package com.atguigu.tingshu.search.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.aggregations.LongTermsAggregate;
import co.elastic.clients.elasticsearch._types.aggregations.LongTermsBucket;
import co.elastic.clients.elasticsearch._types.aggregations.TopHitsAggregate;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.NestedQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.*;
import co.elastic.clients.json.JsonData;
import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.album.client.AlbumInfoFeignClient;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.model.album.BaseCategory3;
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.model.search.SuggestIndex;
import com.atguigu.tingshu.query.search.AlbumIndexQuery;
import com.atguigu.tingshu.search.service.SearchService;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.stream.Collectors;


@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class SearchServiceImpl implements SearchService {

    @Autowired
    private AlbumInfoFeignClient albumInfoFeignClient;

    @Autowired
    private ElasticsearchClient elasticsearchClient;


    @Override
    @SneakyThrows
    public List<Map<String, Object>> channel(Long c1Id) {
        // 1.根据一级分类id 查询该一级分类下自定的7个三级分类（rpc  or  sendMsg）
        Result<List<BaseCategory3>> baseCategory3List = albumInfoFeignClient.getC1Id7TopBaseCategory3List(c1Id);
        List<BaseCategory3> baseCategory3ListData = baseCategory3List.getData();
        Assert.notNull(baseCategory3ListData, "远程调用7个置顶的3级分类对象失败");

        // 2.将7个三级分类对象的id获取到然后转成FiledValue(未来es要用)
        List<FieldValue> c3FieldValueIds = baseCategory3ListData.stream().map(baseCategory3 -> {
            return FieldValue.of(baseCategory3.getId());
        }).collect(Collectors.toList());

        // 3.将7个三级分类对象的集合转成一个Map
        Map<Long, BaseCategory3> category3Map = baseCategory3ListData.stream().collect(Collectors.toMap(BaseCategory3::getId, v -> v));

        // 4.查询es
        // 4.1 构建dsl语句
        SearchRequest request = buildChanelDsl(c3FieldValueIds);

        // 4.2 发送查询请求
        SearchResponse<AlbumInfoIndex> response = elasticsearchClient.search(request, AlbumInfoIndex.class);
        // 4.3 解析数据
        List<Map<String, Object>> result = parseChanelResult(response, category3Map);

        // 5.返回结果
        return result;
    }

    @Override
    @SneakyThrows
    public AlbumSearchResponseVo search(AlbumIndexQuery albumIndexQuery) {
        //1.构建搜索功能DSL语句
        SearchRequest searchRequest = buildSearchConditionDSL(albumIndexQuery);
        //2.开始搜索
        SearchResponse<AlbumInfoIndex> search = elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);
        //3.解析结果

        AlbumSearchResponseVo albumSearchResponseVo = parseResult(search);
        long totalSize = albumSearchResponseVo.getTotal();
        Long totalPages = totalSize % albumIndexQuery.getPageSize() == 0 ? totalSize / albumIndexQuery.getPageSize() : totalSize / albumIndexQuery.getPageSize() + 1;
        albumSearchResponseVo.setTotalPages(totalPages);
        albumSearchResponseVo.setPageNo(albumIndexQuery.getPageNo());
        albumSearchResponseVo.setPageSize(albumIndexQuery.getPageSize());

        return albumSearchResponseVo;
    }

    /**
     * 搜索提示功能
     *
     * @param content
     * @return
     */
    @Override
    @SneakyThrows    // 搜索提示功能DSL语句
    public Set<String> completeSuggest(String content) {
        //1.构建搜索提示词功能DSL语句
        SearchRequest searchRequest = buildSuggestDSL(content);
        //2.发送请求开始搜索
        SearchResponse<SuggestIndex> response = elasticsearchClient.search(searchRequest, SuggestIndex.class);
        //3.解析结果(set集合查询到的不够10个，以搜索内容开头的标题没有十个，用包含关键字的补充)
        Set<String> result = parseSuggestResult(response);
        if (result.size() < 10) {
            //补充搜索提示词
            SearchResponse<SuggestIndex> search = elasticsearchClient.search(sRb -> sRb.index("suggestinfo")
                    .query(qb -> qb
                            .match(mfq -> mfq
                                    .field("title")
                                    .query(content))), SuggestIndex.class);
            search.hits().hits().forEach(hit -> {
                assert hit.source() != null;
                String title = hit.source().getTitle();
                //如果搜索提示词集合小于10，则继续添加搜索提示词
                if (result.size() < 10) {
                    result.add(title);
                }else {
                    return;
                }
            });
        }
        //4.返回结果
        return result;
    }

    /**
     * 解析搜索提示词结果
     *
     * @param response
     * @return
     */
    private Set<String> parseSuggestResult(SearchResponse<SuggestIndex> response) {
        Set<String> result = new HashSet<>();
        Map<String, List<Suggestion<SuggestIndex>>> suggest = response.suggest();
        //有三对key，value值：
        //第一对：key："suggestionKeyword"，value：List<Suggestion<SuggestIndex>>
        //第二对：key："suggestionKeywordSequence"，value：List<Suggestion<SuggestIndex>>
        //第三对：key："suggestionKeywordPinyin"，value：List<Suggestion<SuggestIndex>>
        Set<Map.Entry<String, List<Suggestion<SuggestIndex>>>> entries = suggest.entrySet();
        //遍历
        entries.forEach(entry -> {
            List<Suggestion<SuggestIndex>> value = entry.getValue();
            value.forEach(suggestion -> {
                List<CompletionSuggestOption<SuggestIndex>> options = suggestion.completion().options();
                options.forEach(option -> {
                    SuggestIndex suggestIndex = option.source();
                    assert suggestIndex != null;
                    String title = suggestIndex.getTitle();
                    result.add(title);//不需要高亮
                });
            });
        });
        return result;
    }

    /**
     * 搜索提示功能DSL语句
     *
     * @param content
     * @return
     */
    private SearchRequest buildSuggestDSL(String content) {
        //1.创建一个Suggester.Builder对象
        Suggester.Builder builder = new Suggester.Builder();
        //2.设置suggester的参数
        Suggester suggester = builder.suggesters("suggestionKeyword", fsb -> fsb.prefix(content).completion(csb -> csb.field("keyword").skipDuplicates(true)))
                .suggesters("suggestionKeywordSequence", fsb -> fsb.prefix(content).completion(csb -> csb.field("keywordSequence").skipDuplicates(true)))
                .suggesters("suggestionKeywordPinyin", fsb -> fsb.prefix(content).completion(csb -> csb.field("keywordPinyin").skipDuplicates(true))).build();
        //3.创建SearchRequest对象
        SearchRequest.Builder sRequestBuilder = new SearchRequest.Builder();
        SearchRequest searchRequest = sRequestBuilder.index("suggestinfo").suggest(suggester).build();
        //4.打印搜索提示功能的dsl语句
        log.info("搜索提示功能的dsl语句:{}", searchRequest.toString());
        return searchRequest;
    }

    /**
     * 解析结果
     *
     * @param response
     * @return
     */
    private AlbumSearchResponseVo parseResult(SearchResponse<AlbumInfoIndex> response) {
        AlbumSearchResponseVo searchResponseVo = new AlbumSearchResponseVo();

        TotalHits totalHits = response.hits().total();
        assert totalHits != null;
        long total = totalHits.value();
        ArrayList<AlbumInfoIndexVo> albumInfoIndexVos = new ArrayList<>();

        response.hits().hits().forEach(hit -> {
            AlbumInfoIndex albumInfoIndex = hit.source();
            AlbumInfoIndexVo albumInfoIndexVo = new AlbumInfoIndexVo();
            assert albumInfoIndex != null;
            BeanUtils.copyProperties(albumInfoIndex, albumInfoIndexVo);
            //处理高亮
            Map<String, List<String>> highlight = hit.highlight();
            List<String> albumTitle = highlight.get("albumTitle");
            if (!CollectionUtils.isEmpty(albumTitle)) {
                String highlightTitle = albumTitle.get(0);
                albumInfoIndexVo.setAlbumTitle(highlightTitle);
            }
            albumInfoIndexVos.add(albumInfoIndexVo);
        });
        searchResponseVo.setList(albumInfoIndexVos);
        searchResponseVo.setTotal(total);
        return searchResponseVo;
    }

    /**
     * 构建搜索功能DSL语句
     *
     * @param albumIndexQuery
     * @return
     */
    private SearchRequest buildSearchConditionDSL(AlbumIndexQuery albumIndexQuery) {
        //1.构建搜索功能DSL语句的{}
        SearchRequest.Builder searchRequestBuilder = new SearchRequest.Builder();
        //2.构建boolQueryBuilder对象
        BoolQuery.Builder boolBuilder = new BoolQuery.Builder();
        //3.判断查询条件
        //3.1 判断关键字
        if (albumIndexQuery.getKeyword() != null) {
            boolBuilder.should(qb -> qb.match(mqb -> mqb.field("albumTitle").query(albumIndexQuery.getKeyword())));
            boolBuilder.should(qb -> qb.match(mqb -> mqb.field("albumIntro").query(albumIndexQuery.getKeyword())));
            boolBuilder.should(qb -> qb.match(mqb -> mqb.field("announcerName").query(albumIndexQuery.getKeyword())));
        }
        //3.2 判断分类条件
        if (albumIndexQuery.getCategory1Id() != null) {
            boolBuilder.must(qb -> qb.term(tqb -> tqb.field("category1Id").value(albumIndexQuery.getCategory1Id())));
        }
        if (albumIndexQuery.getCategory2Id() != null) {
            boolBuilder.must(qb -> qb.term(tqb -> tqb.field("category2Id").value(albumIndexQuery.getCategory2Id())));
        }
        if (albumIndexQuery.getCategory3Id() != null) {
            boolBuilder.must(qb -> qb.term(tqb -> tqb.field("category3Id").value(albumIndexQuery.getCategory3Id())));
        }
        //3.3 判断标签信息 属性id:属性值id
        List<String> attributeList = albumIndexQuery.getAttributeList();
        if(!CollectionUtils.isEmpty(attributeList)){
            attributeList.forEach(item -> {
                String[] split = item.split(":");
                String attrId = split[0];
                String attrValueId = split[1];
                //构建nestedQueryBuilder对象
                NestedQuery.Builder nestedQueryBuilder = new NestedQuery.Builder();

                //用nestedQueryBuilder对象构建嵌套查询
                nestedQueryBuilder.path("attributeValueIndexList")
                        .query(qb -> qb.bool(bqb -> bqb
                                .must(mqb -> mqb
                                        .term(tqb -> tqb
                                                .field("attributeValueIndexList.attributeId").value(attrId)))
                                .must(mqb -> mqb
                                        .term(tqb -> tqb
                                                .field("attributeValueIndexList.valueId").value(attrValueId)))));
                //得到nestedQuery对象
                NestedQuery build = nestedQueryBuilder.build();
                boolBuilder.must(qb -> qb.nested(build));
            });
        }

        BoolQuery boolQuery = boolBuilder.build();
        Query query = boolQuery._toQuery();
        searchRequestBuilder.query(query)
                .index("albuminfo");
        //3.4构建分页条件
        searchRequestBuilder.from((albumIndexQuery.getPageNo() - 1) * albumIndexQuery.getPageSize())
                .size(albumIndexQuery.getPageSize());

        //3.5构建排序条件
        String order = albumIndexQuery.getOrder();
        if (!StringUtils.isEmpty(order)){
            String[] split = order.split(":");
            String dimension = split[0];
            String rule = split[1];
            SortOrder realRule = rule.equals("desc") ? SortOrder.Desc : SortOrder.Asc;
            switch (dimension){
                case "1":
                    //热度值排序
                    searchRequestBuilder.sort(srb -> srb.field(f -> f.field("hotScore").order(realRule)));
                    break;
                case "2":
                    searchRequestBuilder.sort(srb -> srb.field(f -> f.field("playStatNum").order(realRule)));
                    //播放量排序
                    break;
                case "3":
                    //发布时间排序
                    searchRequestBuilder.sort(srb -> srb.field(f -> f.field("createTime").order(realRule)));
                    break;
            }
        }else {
            searchRequestBuilder.sort(srb -> srb.field(f -> f.field("hotScore").order(SortOrder.Desc)));
        }
        //3.6构建高亮条件
        searchRequestBuilder.highlight(h -> h
                .fields("albumTitle", fb -> fb
                        .preTags("<font style = 'color:red'>")
                        .postTags("</font>")));

        SearchRequest build = searchRequestBuilder.build();
        System.out.println("打印搜索条件的DSL语句" + build.toString());
        return build;
    }


    /**
     * 解析数据
     * Map:三级分类对象
     *
     * @param response
     * @param category3Map
     * @return
     */
    private List<Map<String, Object>> parseChanelResult(SearchResponse<AlbumInfoIndex> response, Map<Long, BaseCategory3> category3Map) {

        ArrayList<Map<String, Object>> result = new ArrayList<>();
        // 1.根据response对象获取aggregations对象（父aggregations对象）
        Aggregate category3IdAggs = response.aggregations().get("category3IdAggs");
        // 2.将Aggregate转成LongAggregate精准的聚合类型
        LongTermsAggregate lterms = category3IdAggs.lterms();
        // 3.从lterms获取桶
        List<LongTermsBucket> buckets = lterms.buckets().array();
        for (LongTermsBucket bucket : buckets) {
            // map是每一个三级分类对象（为这个Map存两对key,value）
            long c3Id = bucket.key();
            HashMap<String, Object> map = new HashMap<>();
            map.put("baseCategory3", category3Map.get(c3Id));  //baseCategory3这个key的value是该三级分类对象
            Aggregate albumScoreTopAgg = bucket.aggregations().get("albumScoreTopAgg");
            TopHitsAggregate topHitsAggregate = albumScoreTopAgg.topHits();
            List<Hit<JsonData>> hits = topHitsAggregate.hits().hits();
            ArrayList<AlbumInfoIndex> albumInfoIndices = new ArrayList<>();
            for (Hit<JsonData> hit : hits) {
                // hit是每一个热度值高的专辑对象
                JsonData source = hit.source();
                AlbumInfoIndex albumInfoIndex = JSONObject.parseObject(source.toString(), AlbumInfoIndex.class);
                albumInfoIndices.add(albumInfoIndex);
            }
            map.put("list", albumInfoIndices);  //list这个key是这个三级分类下的6个热度值高的专辑集合
            result.add(map);
        }

        // 4.返回结果
        return result;
    }

    /**
     * 构建dsl语句
     *
     * @param c3FieldValueIds
     * @return
     */

    private SearchRequest buildChanelDsl(List<FieldValue> c3FieldValueIds) {

        // 1.构建SearchRequest对象
        SearchRequest searchRequest = SearchRequest.of(b -> b
                .index("albuminfo")
                .query(qb -> qb
                        .terms(tqb -> tqb
                                .field("category3Id")
                                .terms(tqfb -> tqfb
                                        .value(c3FieldValueIds))))
                .aggregations("category3IdAggs", ab -> ab
                        .terms(tab -> tab
                                .field("category3Id")
                                .size(c3FieldValueIds.size()))
                        .aggregations("albumScoreTopAgg", sab -> sab
                                .topHits(thab -> thab
                                        .sort(sob -> sob
                                                .field(fsb -> fsb
                                                        .field("hotScore")
                                                        .order(SortOrder.Desc)))
                                        .size(6)))));

        System.out.println("频道页的dsl:" + searchRequest.toString());
        // 2.返回SearchRequest对象
        return searchRequest;
    }
}
